Add one-click browser account connect for OpenAI, Anthropic, and Google#68
Add one-click browser account connect for OpenAI, Anthropic, and Google#68YishayPo wants to merge 1 commit into
Conversation
|
@yaacovcorcos is there a way to test this before merging to master, how do you build an executable application locally? |
yaacovcorcos
left a comment
There was a problem hiding this comment.
Thanks, Yishay. The product direction is valuable: a clean-machine path from install to the provider's supported sign-in and then verified readiness. I am requesting changes because this PR currently combines several independent, high-risk changes, and the review found confirmed upgrade, permission, authentication, and model-option regressions that green CI does not cover.
Please do not continue repairing this as one mega-PR. Split it into as many independently reviewable PRs as needed. A sensible sequence is:
- One-click connection orchestration only, reusing the existing managed installer and each provider's official login implementation.
- A versioned provider-identity migration that preserves the released meaning of persisted
geminivalues and introduces a distinct identity for native Gemini. - Native Gemini ACP adapter core: sessions, permission policy, Plan-mode behavior, model catalog/options, health, and focused tests.
- Managed Gemini runtime distribution only after the artifact trust/provenance problem is resolved; otherwise require an external official install.
- Onboarding separately: true first-install gating, modal arbitration, platform filtering, truthful credential copy, and browser tests.
- Usage reporting separately, and only through a Google-supported API and credential boundary. The current CLI OAuth piggyback must not ship.
- Remaining settings, plugin-library, icon, and profile integration in small bounded PRs where appropriate.
The blocking findings are in the inline comments. Each replacement PR should include released-version upgrade fixtures where persistence changes, negative permission tests, exact model-option encode/decode/dispatch tests, concurrency/cancellation coverage for connection flows, and clean-machine plus upgrade-path evidence. Please include packaged/manual evidence for user-facing provider flows; compile-only browser fixtures are not sufficient.
Once split, I am happy to review the smaller PRs independently. Do not merge this head while any of the blocking findings remains.
| }), | ||
| ), | ||
| ); | ||
| const PersistedProviderKind = ProviderKind; |
There was a problem hiding this comment.
Blocking — preserve the released provider identity. This removes the migration-aware persisted-provider schema and reinterprets the existing unversioned gemini value as the new native Gemini provider. In released Scient data, that same byte value meant Antigravity. I reproduced this with the same saved settings/model/handoff bytes: the base decodes them as Antigravity, while this head decodes them as native Gemini. That can resume a draft or handoff against the wrong provider/account. Please introduce a distinct persisted discriminator or a versioned migration and apply it consistently to app settings, composer drafts, server model selection, orchestration, and thread handoffs. Add A/B fixtures using actual released payloads. Do not change the historical meaning of gemini in place, and do not update tests to bless the reinterpretation. This migration should be its own PR.
| yield* acp.handleRequestPermission((params) => | ||
| Effect.gen(function* () { | ||
| yield* logNative(input.threadId, "session/request_permission", params); | ||
| if (input.runtimeMode === "full-access") { |
There was a problem hiding this comment.
Blocking — Plan mode must never auto-approve mutations. This branch checks only runtimeMode === "full-access"; it does not check the active interaction mode recorded on the adapter context. A Gemini turn in Plan mode can therefore auto-approve filesystem or command permissions when full-access runtime is selected. Scient's existing invariant is that Plan rejects mutation permissions even under full access. Fail closed on Plan before this auto-approval path and add a real adapter permission test equivalent to the existing Droid negative control.
| if (slug && MODEL_CAPABILITIES_INDEX[provider]?.[slug]) { | ||
| return MODEL_CAPABILITIES_INDEX[provider][slug]; | ||
| } | ||
| if (provider === "gemini") { |
There was a problem hiding this comment.
Blocking — Gemini effort selections do not survive the wire contract. Gemini is routed through the generic effort-descriptor path, which produces reasoningEffort, but the Gemini contract accepts only thinkingLevel (Gemini 3) or thinkingBudget (Gemini 2.5). In exact probes, a selected Gemini 3 LOW was displayed as HIGH after round-trip and a Gemini 2.5 budget of 512 was displayed as -1; the codec stripped both selections to empty options. Add Gemini-specific descriptors keyed by model family and end-to-end tests covering picker selection → encoded contract → decoded selection → adapter dispatch for both Gemini 3 and 2.5. Keep this in the adapter/model-options PR.
| } | ||
|
|
||
| try { | ||
| const loadResult = await fetchJson({ |
There was a problem hiding this comment.
Blocking — remove this OAuth-token usage fetcher. It reads the Gemini CLI OAuth credential and calls private cloudcode-pa.googleapis.com/v1internal endpoints. Google's official Gemini CLI FAQ says third-party software must not harvest/piggyback Gemini CLI OAuth credentials to access backend services and warns this can result in account suspension or termination: https://github.com/google-gemini/gemini-cli/blob/main/docs/resources/faq.md#why-cant-i-use-third-party-software-like-claude-code-openclaw-or-opencode-with-gemini-cli. Launching the official CLI/ACP is a separate supported boundary; reusing its bearer token from Scient is not. Remove/disable this provider-usage implementation. If usage is reintroduced, do it in a separate PR through a documented Google-supported API and credential flow, with explicit product/security review and failure/backoff tests.
| accountId: parseChatGptAccountId(exchange.id_token), | ||
| }; | ||
|
|
||
| yield* writeCodexAuthJson({ codexHome: options.codexHome, tokens }).pipe( |
There was a problem hiding this comment.
Blocking — delegate Codex authentication to the official Codex flow. Scient already invokes codex login; this new 382-line OAuth implementation bypasses Codex's credential-store selection (file/keyring/auto/ephemeral), auth restrictions, configured route/proxy behavior, evolving schema, and concurrency handling. In a disposable full-flow probe, writing into a seeded auth.json removed the existing API key, auth_mode, and unrelated marker and replaced them with raw OAuth tokens. Please remove the native implementation and have one-click connect install/resolve Codex and then invoke the official login/app-server flow. Official implementation reference: https://github.com/openai/codex/blob/main/codex-rs/login/src/server.rs. Keep connection orchestration separate from credential ownership.
| <DialogTitle className="text-[19px] leading-tight">Connect your accounts</DialogTitle> | ||
| <DialogDescription className="text-[14px] leading-[19.5px]"> | ||
| Sign in with the AI accounts you already have. Scient opens each provider’s | ||
| official browser sign-in — no API keys, and your credentials stay with the provider. |
There was a problem hiding this comment.
Blocking product disclosure — this statement is false for this head. The new OpenAI path itself receives and writes raw access, refresh, and ID tokens into CODEX_HOME/auth.json, so credentials do not simply 'stay with the provider.' Gemini usage code also reads the CLI OAuth token. Please remove the native credential handling and Gemini OAuth piggyback as requested above, then use truthful, provider-specific copy describing who stores credentials and where. The UI must not promise a security boundary the implementation does not maintain.
| setOpen: (open) => set(open ? { isOpen: true } : { isOpen: false, provider: null, source: null }), | ||
| beginConnectChain: (provider) => { | ||
| const chain: ProviderConnectChain = { provider, token: crypto.randomUUID() }; | ||
| set({ connectChain: chain }); |
There was a problem hiding this comment.
Required — bind the connection chain to the exact install operation. This is one global {provider, token} slot and is not associated with the managed install's operationId. Starting provider B can overwrite provider A; a stale completion can trigger or clear the wrong chain; and closing/reopening the dialog deliberately leaves the chain alive without an explicit cancellation owner. Represent the chain with provider + exact operationId + lifecycle state, define replacement/cancellation semantics, and test concurrent providers, stale completion, failure/retry, and dialog close/reopen. This belongs in the small connection-orchestration PR.
| placeholder: "Model Name", | ||
| example: "Gemini 4 Pro", | ||
| }, | ||
| gemini: { |
There was a problem hiding this comment.
Required — the custom Gemini settings path is only partially wired. This makes Gemini appear in the generic custom-model editor, but the settings route's onValueChange provider allowlist omits gemini, so selecting the visible Gemini entry is rejected. The reset/dirty-state paths also omit Gemini fields (including customGeminiModels and geminiBinaryPath in relevant aggregates). Please use one exhaustive provider-keyed source rather than duplicated allowlists, cover save/reset/dirty behavior, and add a browser test that actually edits and persists a Gemini model. This can be a separate settings-integration PR.
| const antigravityCapabilitiesQuery = useQuery( | ||
| providerComposerCapabilitiesQueryOptions("antigravity"), | ||
| ); | ||
| const geminiCapabilitiesQuery = useQuery(providerComposerCapabilitiesQueryOptions("gemini")); |
There was a problem hiding this comment.
Required — this query cannot make Gemini selectable in the library. Gemini is added to the capability map but omitted from PROVIDER_DISCOVERY_ORDER, which drives provider fallback/selection. The integration is therefore internally inconsistent and future Gemini discovery support remains invisible. Either omit this inactive integration from the adapter PR or add Gemini through an exhaustive provider list and test provider selection/fallback. Please keep plugin-library work separate from core ACP transport.
| export const Gemini: Icon = (props) => ( | ||
| <svg {...props} viewBox="0 0 296 298" fill="none"> | ||
| <mask | ||
| id="gemini__a" |
There was a problem hiding this comment.
Required — make SVG definitions instance-safe and document provenance. These fixed mask/filter IDs are duplicated whenever more than one Gemini icon renders, so references can collide across instances. Use useId (as other reusable icons do) or remove the filter-ID dependency. This artwork also matches the donor implementation; before landing, record its source/lineage and verify that its license/brand-use terms are compatible with this repository, including the two copied public SVGs. Icon/provenance cleanup does not need to ride with provider behavior.
Silent managed installs chain straight into each provider's browser sign-in; adds native ChatGPT OAuth and first-run account onboarding.
3b0bf44 to
968fced
Compare
There was a problem hiding this comment.
Thanks for reducing the branch, Yishay. I re-reviewed the current head 968fcedd. The removed Gemini-specific findings from the earlier review are now outdated; the remaining one-click connection work is valuable and worth finishing.
I am still requesting changes because the current head retains several release-blocking authentication, lifecycle, onboarding, and integration issues. Please use this as the current checklist:
-
Remove the native Codex/OpenAI OAuth implementation. Delete
openaiNativeOAuth.tsand its dedicated flow tests. The one-click sequence should be: install or resolve the managed Codex runtime → invoke the officialcodex loginbrowser flow → verify authentication → require a non-empty model catalog before reporting success. Native OAuth only gains parallel install/authentication and tighter callback-page control. Those modest UX gains do not justify making Scient responsible for OpenAI tokens, Codex's evolvingauth.jsonschema, keyring/file/ephemeral credential-store selection, auth restrictions, proxies/routes, and concurrent-login behavior. The existing implementation demonstrably replaces unrelated seeded credential fields, and the active inline thread also identifies callback-state and premature-success bugs. The user can still have a one-button browser-login experience through the official CLI. -
Make install-to-login chaining operation-owned and failure-safe. Bind the chain to the exact provider installation
operationId, keep it alive until the server accepts the login operation, define cancellation/replacement semantics, and expose terminal failure/retry state even when the dialog is closed. Starting provider B must not overwrite provider A, and stale completion must not trigger or clear another operation. -
Harden or separate onboarding while keeping “Skip for now.” It needs durable first-install versus upgrade provenance, centralized startup-modal arbitration, platform-aware provider filtering, and truthful credential copy. Existing users must not receive first-run onboarding merely because the new local-storage key is absent. Keep the Skip for now action: skipping onboarding and providing permanent ways to connect later are separate product concerns. After a user skips with no provider connected, attempting to send a message must explain that no provider is connected, preserve the draft, and offer a direct Connect a provider action. The provider/model selector must clearly show disconnected or uninstalled providers and provide inline Connect or Set up actions. Settings → Providers must remain permanently available as another connection entry point. Please add component/browser coverage for fresh install, upgrade, already-connected, skipped/completed, contextual send recovery, selector connection, More Providers, and competing startup-dialog states.
-
Remove unrelated changes from this PR. The raw ACP
session/set_modelfallback and settings/composer refactors are not required for one-click connection or onboarding. Move the ACP compatibility behavior into a separate typed, capability-gated, directly tested PR. Keep the low-risk refactors separate as well so this authentication change remains reviewable. -
Rebase onto current
maincarefully. The branch currently conflicts in__root.tsx. Main intentionally removedAppSnapWelcomeDialogat startup; the resolution must preserve that removal and must not reintroduce the startup snapshot notification. -
Complete failure-path and user-journey evidence. Add coverage for installed and uninstalled providers, clean install → official browser login → account/model verification, browser-open failure with manual retry, auth denial/cancellation, install failure, verification failure, concurrent providers, stale events, dialog close/reopen, onboarding upgrade behavior, skipped onboarding followed by a send attempt, selector-driven connection, and preserving an unsent draft through connection cancellation or failure. Include packaged/manual evidence on the supported desktop platforms for the final user-facing flow; green unit tests alone do not prove the browser/CLI integration.
-
Update the PR description. It still says the reduced head adds Gemini CLI, while the current 17-file diff does not. Fill in What Changed/Why, document the remaining scope, and include screenshots plus a short interaction recording for onboarding and one-click connection.
The target outcome is excellent: one click from an unconfigured provider to its official browser sign-in, followed by verified account and model readiness. The changes above preserve that product value while keeping credentials owned by the provider CLI and making failures recoverable and observable. Please do not merge the current head until the active current-head threads and the inline findings below are addressed.
| autoOpenedAuthorizationOperationRef.current = operationId; | ||
| void ensureNativeApi() | ||
| .shell.openExternal(authorizationUrl) | ||
| .catch(() => undefined); |
There was a problem hiding this comment.
Required — do not silently swallow browser-launch failure. If openExternal rejects, the operation is marked as already auto-opened and the error disappears. Record an observable failure/warning on the connection operation and keep a clear manual Open browser again action. Ensure a failed automatic launch can be retried and add a browser test for openExternal rejection. This matters even after switching Codex back to official codex login, because every browser-auth provider needs a recoverable launch failure.
| if (!connectChain) return; | ||
| const decision = decideConnectChainStep(chainStatus); | ||
| if (decision === "wait") return; | ||
| clearConnectChain(connectChain.token); |
There was a problem hiding this comment.
Required — do not clear the chain before the login handoff succeeds. The chain is deleted before startProviderConnection is accepted. If that call fails, retry ownership is lost; when the dialog is closed, setActionError is also not visible to the user. Keep the exact operation active through the handoff, transition it to a durable terminal failure/retry state on rejection, and clear it only after the server acknowledges the new login operation or the user explicitly cancels it. Cover closed-dialog failure, retry, stale completion, and concurrent providers.
| : runLoggedRequest( | ||
| "session/set_model", | ||
| { sessionId: started.sessionId, modelId: model }, | ||
| acp.raw.request("session/set_model", { |
There was a problem hiding this comment.
Required scope/compatibility fix — split this from the connection PR. This introduces a raw call to the explicitly unstable session/set_model method when no config ID exists. It bypasses the typed client surface, is not capability-gated, and has no direct test for unsupported agents, protocol failure, or successful model application. Move it to a focused ACP compatibility PR; use the typed setSessionModel surface where available, gate it on advertised support, define the unsupported fallback, and add direct protocol tests before shipping it.
| <ConnectionRecoveryNotifications /> | ||
| <GlobalWhatsNewSurface /> | ||
| <TaskCompletionNotifications /> | ||
| <OnboardingConnectAccounts /> |
There was a problem hiding this comment.
Rebase blocker — preserve current main’s AppSnap removal and arbitrate startup overlays. This branch now conflicts with main here. Main intentionally removed AppSnapWelcomeDialog; resolving by taking the branch block would restore the startup snapshot notification. Keep that dialog removed. Also avoid simply stacking onboarding beside GlobalWhatsNewSurface and other root dialogs: route startup surfaces through explicit modal arbitration so only one can open at a time, with a test for competing eligible dialogs.
|
@YishayPo Product clarification for onboarding: please keep “Skip for now.” I removed the earlier inline objection to that action and updated the main review. Skipping onboarding and having permanent ways to connect later are separate concerns. The desired behavior after a user skips without connecting a provider is:
The onboarding modal can remain lightweight and dismissible. The rest of the app must still make the next required action obvious at the moment the user tries to use a provider. |
What changed
One click takes an unconfigured provider from "not installed" to a verified, ready account:
The install→sign-in handoff is an operation-owned chain: bound to the exact managed-install
operationId, so concurrent providers never interfere, stale completions can't trigger/clear the
wrong flow, and the chain survives closing the dialog. Terminal failures are observable (Activity
Center) and retryable even with the dialog closed. Credentials stay with each provider's own CLI.
Scope
In this PR: one-click install-and-connect orchestration, per-provider operation-owned chains,
failure/retry surfacing, presentation logic + unit/browser coverage.
Not in this PR (follow-ups): first-run onboarding (preserved on
onboarding-connect-accounts-wip);ACP session/set_model fallback; settings/composer refactors; native OpenAI OAuth (dropped for the
official
codex loginflow).